package org.connectbot; import org.connectbot.util.HostDatabase; import org.connectbot.util.PreferenceConstants; import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; import android.support.annotation.ColorRes; import android.support.annotation.StringRes; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.UiController; import android.support.test.espresso.ViewAction; import android.support.test.espresso.action.CloseKeyboardAction; import android.support.test.espresso.intent.Intents; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.view.View; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.longClick; import static android.support.test.espresso.action.ViewActions.pressBack; import static android.support.test.espresso.action.ViewActions.swipeDown; import static android.support.test.espresso.action.ViewActions.swipeUp; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem; import static android.support.test.espresso.intent.Intents.intended; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.connectbot.ConnectbotMatchers.hasHolderItem; import static org.connectbot.ConnectbotMatchers.withColoredText; import static org.connectbot.ConnectbotMatchers.withConnectedHost; import static org.connectbot.ConnectbotMatchers.withDisconnectedHost; import static org.connectbot.ConnectbotMatchers.withHostNickname; import static org.hamcrest.CoreMatchers.allOf; @RunWith(AndroidJUnit4.class) public class StartupTest { /** * The delay time to allow the soft keyboard to dismiss. */ private static final long KEYBOARD_DISMISSAL_DELAY_MILLIS = 1000L; @Rule public final ActivityTestRule<HostListActivity> mActivityRule = new ActivityTestRule<>( HostListActivity.class, false, false); @Before public void makeDatabasePristine() { Context testContext = InstrumentationRegistry.getTargetContext(); HostDatabase.resetInMemoryInstance(testContext); mActivityRule.launchActivity(new Intent()); } @Test public void canToggleSoftKeyboardVisibility() { // First change preferences so that show/hide keyboard button will not auto-hide Context testContext = InstrumentationRegistry.getTargetContext(); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(testContext); SharedPreferences.Editor editor = settings.edit(); boolean wasAlwaysVisible = settings.getBoolean(PreferenceConstants.KEY_ALWAYS_VISIVLE, false); editor.putBoolean(PreferenceConstants.KEY_ALWAYS_VISIVLE, true).apply(); startNewLocalConnection(); hideAndShowSoftKeyboard(); editor.putBoolean(PreferenceConstants.KEY_ALWAYS_VISIVLE, wasAlwaysVisible).commit(); } @Test public void localConnectionDisconnectFromHostList() { startNewLocalConnection(); onView(withId(R.id.console_flip)).perform(closeSoftKeyboard(), pressBack()); // Make sure we're still connected. onView(withId(R.id.list)) .check(hasHolderItem(allOf(withHostNickname("Local"), withConnectedHost()))) .perform(actionOnHolderItem( allOf(withHostNickname("Local"), withConnectedHost()), longClick())); // Click on the disconnect context menu item. onView(withText(R.string.list_host_disconnect)).check(matches(isDisplayed())).perform(click()); // Now make sure we're disconnected. onView(withId(R.id.list)).check(hasHolderItem(allOf(withHostNickname("Local"), withDisconnectedHost()))); } @Test public void localConnectionDisconnectConsoleActivity() { startNewLocalConnection(); openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext()); // Click on the disconnect context menu item. onView(withText(R.string.list_host_disconnect)).check(matches(isDisplayed())).perform(click()); // Now make sure we're disconnected. onView(withId(R.id.list)).check(hasHolderItem(allOf(withHostNickname("Local"), withDisconnectedHost()))); } @Test public void localConnectionCanDelete() { startNewLocalConnectionAndGoBack("Local"); onView(withId(R.id.list)).perform(actionOnHolderItem(withHostNickname("Local"), longClick())); onView(withText(R.string.list_host_delete)).perform(click()); onView(withText(R.string.delete_pos)).perform(click()); } @Test public void localConnectionCanChangeToRed() { startNewLocalConnectionAndGoBack("RedLocal"); changeColor("RedLocal", R.color.red, R.string.color_red); } @Test public void canScrollTerminal() { startNewLocalConnection(); onView(withId(R.id.terminal_view)) .perform(closeSoftKeyboard(), longClick(), swipeUp(), swipeDown()); } @Test public void addHostThenCancelAndDiscard() { onView(withId(R.id.add_host_button)).perform(click()); onView(withId(R.id.quickconnect_field)).perform(typeText("abandoned"), closeSoftKeyboard(), pressBack()); onView(withText(R.string.discard_host_changes_message)).check(matches(isDisplayed())); onView(withText(R.string.discard_host_button)).perform(click()); onView(withId(R.id.list)).check(matches(isDisplayed())); } @Test public void addHostThenCancelAndKeepEditing() { onView(withId(R.id.add_host_button)).perform(click()); onView(withId(R.id.quickconnect_field)).perform(typeText("abandoned"), closeSoftKeyboard(), pressBack()); onView(withText(R.string.discard_host_changes_message)).check(matches(isDisplayed())); onView(withText(R.string.discard_host_cancel_button)).perform(click()); onView(withId(R.id.quickconnect_field)).check(matches(isDisplayed())); } /** * Changes the color of {@code hostName} from the {@link HostListActivity} to the {@code color} * from {@code R.color.[color]} with identifying {@code stringForColor} from * {@code R.string.[colorname]}. */ private void changeColor(String hostName, @ColorRes int color, @StringRes int stringForColor) { // Bring up the context menu. onView(withId(R.id.list)).perform(actionOnHolderItem(withHostNickname(hostName), longClick())); onView(withText(R.string.list_host_edit)).perform(click()); // Click on the color category and select the desired one. onView(withText(R.string.hostpref_color_title)).perform(click()); onView(withText(stringForColor)).perform(click()); // Go back to the host list. onView(withId(R.id.save)).perform(click()); Resources res = InstrumentationRegistry.getTargetContext().getResources(); onView(withId(R.id.list)).check(hasHolderItem(withColoredText(res.getColor(color)))); } private void hideAndShowSoftKeyboard() { onView(withId(R.id.console_flip)).perform(closeSoftKeyboard()); onView(withContentDescription(R.string.image_description_show_keyboard)).perform(click()); onView(withId(R.id.console_flip)).perform(loopMainThreadFor(KEYBOARD_DISMISSAL_DELAY_MILLIS)); onView(withContentDescription(R.string.image_description_hide_keyboard)).perform(click()); onView(withId(R.id.console_flip)).perform(loopMainThreadFor(KEYBOARD_DISMISSAL_DELAY_MILLIS)); onView(withContentDescription(R.string.image_description_show_keyboard)).perform(click()); onView(withId(R.id.console_flip)).perform(pressBack()); } private void startNewLocalConnectionAndGoBack(String name) { startNewLocalConnection(name); onView(withId(R.id.console_flip)).perform(closeSoftKeyboard(), pressBack()); onView(withId(R.id.list)).check(hasHolderItem(withHostNickname(name))); } private void startNewLocalConnection() { startNewLocalConnection("Local"); } private void startNewLocalConnection(String name) { onView(withId(R.id.add_host_button)).perform(click()); onView(withId(R.id.protocol_text)).perform(click()); onView(withText("local")).perform(click()); onView(withId(R.id.quickconnect_field)).perform(typeText(name)); onView(withId(R.id.save)).perform(click()); Intents.init(); try { onView(withId(R.id.list)).perform(actionOnHolderItem( withHostNickname(name), click())); intended(hasComponent(ConsoleActivity.class.getName())); } finally { Intents.release(); } onView(withId(R.id.console_flip)).check(matches( hasDescendant(allOf(isDisplayed(), withId(R.id.terminal_view))))); } /* * This is to work around a race condition where the software keyboard does not dismiss in time * and you get a Security Exception. * * From: https://code.google.com/p/android-test-kit/issues/detail?id=79#c7 */ public static ViewAction closeSoftKeyboard() { return new ViewAction() { /** * The real {@link CloseKeyboardAction} instance. */ private final ViewAction mCloseSoftKeyboard = new CloseKeyboardAction(); @Override public Matcher<View> getConstraints() { return mCloseSoftKeyboard.getConstraints(); } @Override public String getDescription() { return mCloseSoftKeyboard.getDescription(); } @Override public void perform(final UiController uiController, final View view) { mCloseSoftKeyboard.perform(uiController, view); uiController.loopMainThreadForAtLeast(KEYBOARD_DISMISSAL_DELAY_MILLIS); } }; } public static ViewAction loopMainThreadFor(final long millis) { return new ViewAction() { @Override public Matcher<View> getConstraints() { return isEnabled(); } @Override public String getDescription() { return "Rturns an action that loops the main thread for at least " + millis +"ms."; } @Override public void perform(final UiController uiController, final View view) { uiController.loopMainThreadForAtLeast(millis); } }; } }